﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Xml;
using Storm.TextEditor.Editor;
using Storm.TextEditor.Editor.Text;
using Storm.TextEditor.Parser.Objects;
using Storm.TextEditor.Parser.Objects.Collections;
using DevExpress.XtraBars;
using DevExpress.XtraEditors;
using DevExpress.XtraGrid.Views.Base;
using Moonlite.Compiling;
using Moonlite.Services.CodeCompletion;
using Moonlite.ExtensionApi.ControlInterfaces;
using Moonlite.ExtensionApi.GlobalInterfaces;
using Moonlite.Platform.General;
using Moonlite.Platform.Project;

namespace Moonlite.UI.Shell.Controls
{
    public partial class ErrorList : UserControl, IErrorList
    {
        private WordCollection errorWords = new WordCollection();
        public event EventHandler ErrorsUpdated;
        public const int ImageIndexError = 0;
        public const int ImageIndexWarning = 0;
        public const int ImageIndexMessage = 0;
        public bool UsePeriodicSyntaxCheck { get; set; }
        private bool JassHelperThreadFinished { get; set; }
        private static string JassHelperLogPath { get; set; }
        private BackgroundWorker Worker { get; set; }
        private ErrorListObjectCollection errors = new ErrorListObjectCollection();
        public ErrorListObjectCollection Errors
        {
            get { return errors; }
            set { errors = value; }
        }

        private ErrorListObjectCollection warnings = new ErrorListObjectCollection();
        public ErrorListObjectCollection Warnings
        {
            get { return warnings; }
            set { warnings = value; }
        }

        private ErrorListObjectCollection messages = new ErrorListObjectCollection();
        public ErrorListObjectCollection Messages
        {
            get { return messages; }
            set { messages = value; }
        }

        public ErrorListObjectCollection AllObjects
        {
            get
            {
                ErrorListObjectCollection objects = new ErrorListObjectCollection();
                if (btnToggleErrors.Down == true)
                {
                    objects.AddRange(Errors.ToArray());
                }

                if (btnToggleWarnings.Down == true)
                {
                    objects.AddRange(Warnings.ToArray());
                }

                if (btnToggleMessages.Down == true)
                {
                    objects.AddRange(Messages.ToArray());
                }

                return objects;
            }
        }

        protected virtual void OnErrorsUpdated(EventArgs e)
        {
            if (ErrorsUpdated != null)
            {
                ErrorsUpdated(this, e);
            }
        }

        public ErrorListObjectCollection CombineWithOtherCollections(ErrorListObjectType type, ErrorListObjectCollection collection)
        {
            if (type == ErrorListObjectType.Error)
            {
                collection.AddRange(Warnings.ToArray());
                collection.AddRange(Messages.ToArray());
            }
            else if (type == ErrorListObjectType.Warning)
            {
                collection.AddRange(Errors.ToArray());
                collection.AddRange(Messages.ToArray());
            }
            else if (type == ErrorListObjectType.Message)
            {
                collection.AddRange(Errors.ToArray());
                collection.AddRange(Warnings.ToArray());
            }

            return collection;
        }

        public bool AddObject(ErrorListObjectType type, ErrorListObject listObject)
        {
            listObject.ImageIndex = (int)type;
            ErrorListObjectCollection realCollection = null;
            ErrorListObjectCollection collection = null;
            if (type == ErrorListObjectType.Error)
            {
                collection = new ErrorListObjectCollection(Errors);
                realCollection = Errors;
            }
            else if (type == ErrorListObjectType.Warning)
            {
                collection = new ErrorListObjectCollection(Warnings);
                realCollection = Warnings;
            }
            else if (type == ErrorListObjectType.Message)
            {
                collection = new ErrorListObjectCollection(Messages);
                realCollection = Messages;
            }

            ErrorListObjectCollection allObjects = CombineWithOtherCollections(type, collection);
            if (collection == null || allObjects.Contains(listObject) == true || string.IsNullOrEmpty(listObject.Description) == true ||
                string.IsNullOrEmpty(listObject.File) == true || listObject.Row == -1 || listObject.Column == -1 || listObject.Length == -1)
            {
                return false;
            }

            collection.Add(listObject);
            realCollection.Add(listObject);
            RefreshObjects();
            return true;
        }

        public bool AddObject(ErrorListObjectType type, string description, string file, int row, int column, int length, ErrorListObjectDoubleClick action)
        {
            return AddObject(type, new ErrorListObject(-1, description, file, row, column, length, action));
        }

        private ErrorListObjectCollection GetTextEditorErrors(TextEditor editor)
        {
            ErrorListObjectCollection errors = new ErrorListObjectCollection();
            foreach (ErrorListObject error in AllObjects)
            {
                if (error.File == editor.InnerTextEditor.FileName)
                {
                    errors.Add(error);
                }
            }

            return errors;
        }

        private WordCollection GetWordsAtRange(SyntaxDocument document, int row, int column, int length)
        {
            WordCollection words = new WordCollection();
            int currentIndex = 0;
            int currentRow = row;
            int offset = column;
            while (true)
            {
                Word word = document[currentRow][currentIndex];
                if (word == null)
                {
                    break;
                }

                if (!(word.Column >= column))
                {
                    currentIndex++;
                    offset += word.Text.Length;
                    continue;
                }

                if (!words.Contains(word) && word.Text.Length != 0)
                {
                    words.Add(word);
                }

                currentIndex++;
                offset += word.Text.Length;
                if (offset >= length)
                {
                    break;
                }

                if (offset >= document[currentRow].Text.Length)
                {
                    currentIndex = 0;
                    currentRow++;
                }
            }

            return words;
        }

        public void RefreshUnderlines()
        {
            foreach (Word word in errorWords)
            {
                word.HasError = false;
                word.HasWarning = false;
            }

            errorWords.Clear();
            foreach (TextEditor editor in Global.Instance.TextEditors)
            {
                ErrorListObjectCollection errors = GetTextEditorErrors(editor);
                if (errors != null && errors.Count > 0)
                {
                    SyntaxDocument document = editor.InnerTextEditor.Document;
                    AndromedaSource source = GalaxyObjectHelper.GetSourceFromFileName(editor.InnerTextEditor.FileName, GalaxyCompletionDataProvider.LastStructure);
                    if (document != null && source != null)
                    {
                        foreach (ErrorListObject error in errors)
                        {
                            int row = error.Row - 1;
                            int column = error.Column;
                            WordCollection words = GetWordsAtRange(document, row, column, error.Length);
                            foreach (Word word in words)
                            {
                                if (word != null)
                                {
                                    if (Errors.Contains(error))
                                    {
                                        word.HasError = true;
                                        word.ErrorColor = Color.Red;
                                    }

                                    if (Warnings.Contains(error))
                                    {
                                        word.HasWarning = true;
                                        word.WarningColor = Color.YellowGreen;
                                    }

                                    if (Messages.Contains(error))
                                    {
                                        word.HasWarning = true;
                                        word.WarningColor = Color.Blue;
                                    }

                                    if (!errorWords.Contains(word))
                                    {
                                        errorWords.Add(word);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        public void RefreshObjects()
        {
            btnToggleErrors.Caption = Errors.Count.ToString() + " Errors";
            btnToggleWarnings.Caption = Warnings.Count.ToString() + " Warnings";
            btnToggleMessages.Caption = Messages.Count.ToString() + " Messages";
            gridControl.DataSource = AllObjects;
            RefreshUnderlines();
        }

        private void OnGridViewCustomDrawCell(object sender, RowCellCustomDrawEventArgs e)
        {
            if (e.Column == gridColIcon)
            {
                ImageList images = (gridView.Images as ImageList);
                Point location = e.Bounds.Location;
                int widthOffset = e.Bounds.Width - images.ImageSize.Width;
                int heightOffset = e.Bounds.Height - images.ImageSize.Height;
                if (e.Bounds.Width > images.ImageSize.Width)
                {
                    location.X += widthOffset / 2;
                }

                if (e.Bounds.Height > images.ImageSize.Height)
                {
                    location.Y += heightOffset / 2;
                }

                int index = e.CellValue is int ? (int)e.CellValue : -1;
                if (index == -1)
                {
                    return;
                }

                images.Draw(e.Graphics, location, index);
                e.Handled = true;
            }
        }

        private void OnGridViewFocusedRowChanged(object sender, FocusedRowChangedEventArgs e)
        {
            gridView.FocusedColumn = gridColIcon;
        }

        private void OnGridViewFocusedColumnChanged(object sender, FocusedColumnChangedEventArgs e)
        {
            gridView.FocusedColumn = gridColIcon;
        }

        private void OnToggleErrorsItemClick(object sender, ItemClickEventArgs e)
        {
            RefreshObjects();
        }

        private void OnToggleWarningsItemClick(object sender, ItemClickEventArgs e)
        {
            RefreshObjects();
        }

        private void OnToggleMessagesItemClick(object sender, ItemClickEventArgs e)
        {
            RefreshObjects();
        }

        public void ReadAndromedaLog()
        {
            if (Global.Instance.Project == null)
            {
                return;
            }

            if (Global.Instance.Project.Type == ProjectType.Galaxy)
            {
                string filePath = Global.GetAndromedaOutputFolderPath() + "\\" + Global.AndromedaErrorsFileName;
                if (File.Exists(filePath) == false) return;
                try
                {
                    XmlDocument document = new XmlDocument();
                    document.Load(filePath);
                    foreach (XmlNode node in document.ChildNodes)
                    {
                        if (node.Name.ToLower() == "andromedaparseresult")
                        {
                            foreach (XmlNode childNode in node.ChildNodes)
                            {
                                if (childNode.Name.ToLower() == "messages")
                                {
                                    foreach (XmlNode messageNode in childNode.ChildNodes)
                                    {
                                        if (messageNode.Name.ToLower() == "message")
                                        {
                                            ErrorListObject message = new ErrorListObject();
                                            ErrorListObjectType type = ErrorListObjectType.Message;
                                            foreach (XmlAttribute attribute in messageNode.Attributes)
                                            {
                                                switch (attribute.Name.ToLower())
                                                {
                                                    case "text":
                                                        message.Description = attribute.Value;
                                                        break;
                                                    case "severity":
                                                        switch (attribute.Value.ToLower())
                                                        {
                                                            case "error":
                                                                type = ErrorListObjectType.Error;
                                                                break;
                                                            case "warning":
                                                                type = ErrorListObjectType.Warning;
                                                                break;
                                                            case "info":
                                                                type = ErrorListObjectType.Message;
                                                                break;
                                                        }

                                                        break;
                                                }
                                            }

                                            message.Action = OnAndromedaErrorDoubleClick;
                                            foreach (XmlNode locationNode in messageNode.ChildNodes)
                                            {
                                                if (locationNode.Name.ToLower() == "location")
                                                {
                                                    foreach (XmlAttribute attribute in locationNode.Attributes)
                                                    {
                                                        switch (attribute.Name.ToLower())
                                                        {
                                                            case "type":
                                                                break;
                                                            case "name":
                                                                break;
                                                            case "path":
                                                                message.File = attribute.Value.Replace(Uri.UriSchemeFile + "://", "");
                                                                break;
                                                            case "line":
                                                                message.Row = int.Parse(attribute.Value);
                                                                break;
                                                            case "column":
                                                                message.Column = int.Parse(attribute.Value);
                                                                break;
                                                            case "length":
                                                                message.Length = int.Parse(attribute.Value);
                                                                break;
                                                            case "offset":
                                                                break;
                                                        }
                                                    }
                                                }
                                            }

                                            message = ProcessDscFormatTags(message);
                                            AndromedaSource source = GalaxyObjectHelper.GetSourceFromFileName(message.File, GalaxyCompletionDataProvider.LastStructure);
                                            if (source != null)
                                            {
                                                message.Row -= (source.TagLength > 0 ? 1 : 0);
                                            }

                                            AddObject(type, message);
                                        }
                                    }
                                }
                            }
                        }
                    }

                    RefreshObjects();
                }
                catch (Exception exception)
                {
                    Logger.Log += exception.Message + Environment.NewLine;
                }
            }
        }

        private ErrorListObject ProcessDscFormatTags(ErrorListObject message)
        {
            string fileContents = "";
            using (StreamReader reader = new StreamReader(message.File))
            {
                fileContents = reader.ReadToEnd();
            }

            string[] lines = fileContents.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
            if (lines.Length > 0)
            {
                string tagLine = lines[0];
                if (tagLine.StartsWith("//DSCFormat:"))
                {
                    string[] tags = tagLine.Replace("//DSCFormat:", "").Split(';');
                    foreach (string tag in tags)
                    {
                        string[] equalSplit = tag.Split('=');
                        if (equalSplit.Length == 2)
                        {
                            string key = equalSplit[0];
                            string value = equalSplit[1];
                            switch (key)
                            {
                                case "Path":
                                    message.File = value;
                                    break;
                                case "Project":
                                    break;
                            }
                        }
                    }
                }
            }

            return message;
        }

        public void ReadJassHelperLog()
        {
            if (Global.Instance.Project == null)
            {
                return;
            }

            if (Global.Instance.Project.Type == ProjectType.Jass)
            {
                if (File.Exists(JassHelperLogPath) == false) return;
                try
                {
                    using (StreamReader logReader = new StreamReader(JassHelperLogPath))
                    {
                        string contents = logReader.ReadToEnd();
                        string[] lines = contents.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                        for (int lineIndex = 2; lineIndex < lines.GetLength(0) - 1; lineIndex++)
                        {
                            ReadLogLine(lines[lineIndex]);
                        }
                    }
                }
                catch (Exception exception)
                {
                    Logger.Log += exception.Message + Environment.NewLine;
                }
            }
        }

        private void ReadLogLine(string line)
        {
            string description = "";
            int rowIndex = line.IndexOf("Line ");
            int row = -1;
            if (rowIndex > -1)
            {
                int colonIndex = line.IndexOf(":", rowIndex + "Line ".Length);
                if (colonIndex > -1)
                {
                    string rowString = line.Substring(rowIndex + "Line ".Length, colonIndex - (rowIndex + "Line ".Length));
                    row = Convert.ToInt32(rowString);
                }

                description = line.Substring(colonIndex + 1).Trim();
            }

            if (description == "" || row == -1)
            {
                return;
            }

            // Since the Error is from JassHelper, we can't provide a file.
            ErrorListObject listObject = new ErrorListObject(ImageIndexError, description, "", row, 0, 0, OnJassHelperErrorDoubleClick);
            Errors.Add(listObject);
            RefreshObjects();
        }

        private void OnWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (Global.Instance == null || Global.Instance.Project == null)
            {
                return;
            }

            if (Global.Instance.Project.Type == ProjectType.Jass)
            {
                ReadJassHelperLog();
                JassHelperThreadFinished = true;
            }
            else if (Global.Instance.Project.Type == ProjectType.Galaxy)
            {
                ReadAndromedaLog();
                GalaxyCompletionDataProvider.Update();
            }
        }

        private void OnWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            if (Global.Instance.Project.Type == ProjectType.Jass)
            {
                CompilerManager.ExecuteCLIJassHelper();
            }
            else if (Global.Instance.Project.Type == ProjectType.Galaxy)
            {
                CompilerManager.ExecuteAndromeda(Global.Instance.Project, true, true, false, true, "");
            }
        }

        private void OnReadLogTimerTick(object sender, EventArgs e)
        {
            if (!DesignMode)
            {
                Errors.Clear();
                Warnings.Clear();
                Messages.Clear();
                OnErrorsUpdated(EventArgs.Empty);
                IProject project = Global.Instance.Project;
                if (UsePeriodicSyntaxCheck == true && gridControl.Focused == false && Global.Instance != null && DesignMode == false && project != null && !Worker.IsBusy)
                {
                    Errors.Clear();
                    if (Global.Instance.Project.Type == ProjectType.Jass && JassHelperThreadFinished == true)
                    {
                        JassHelperThreadFinished = false;
                        Worker.RunWorkerAsync();
                    }
                    else if (Global.Instance.Project.Type == ProjectType.Galaxy)
                    {
                        Worker.RunWorkerAsync();
                    }
                }
            }
        }

        private void OnJassHelperErrorDoubleClick(ErrorListObject @object)
        {
            if (@object.ImageIndex == ImageIndexError)
            {
                Global.Instance.OpenCurrentMapScript(((int)gridView.GetFocusedRowCellValue(gridColRow)) - 1, true);
                return;
            }
        }

        private void OnAndromedaErrorDoubleClick(ErrorListObject @object)
        {
            int line = @object.Row - 1;
            int column = @object.Column;
            string file = @object.File;
            if (line > -1 && column > -1 && string.IsNullOrEmpty(file) == false)
            {
                ITextEditor textEditor = Global.Instance.OpenFile(file, new ProjectFile() { Path = Path.GetDirectoryName(file), Name = Path.GetFileName(file) });
                if (textEditor != null)
                {
                    textEditor.InnerTextEditor.Caret.Position.X = column;
                    textEditor.InnerTextEditor.Caret.Position.Y = line;
                    textEditor.InnerTextEditor.ActiveViewControl.CenterInView(line);
                    Moonlite.Shell.SelectLine(textEditor);
                }
            }
        }

        private void OnGridControlMouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (gridView.FocusedRowHandle <= -1 || e.Button != MouseButtons.Left)
            {
                return;
            }

            object errorObject = gridView.GetFocusedRow();
            ErrorListObject listObject = (ErrorListObject)errorObject;
            if (listObject == null) return;
            listObject.Action.Invoke(listObject);
        }

        private void OnRefreshItemClick(object sender, ItemClickEventArgs e)
        {
            OnReadLogTimerTick(this, EventArgs.Empty);
        }

        public ErrorList()
        {
            InitializeComponent();
            try { Global.Instance.RegisterService(this, GetType().GetInterface("IErrorList")); }
            catch { }
            DynamicSyntaxCheckingScheduler.CheckTimeElapsed += OnReadLogTimerTick;
            JassHelperThreadFinished = true;
            JassHelperLogPath = Application.StartupPath + "\\logs\\compileerrors.txt";
            Worker = new BackgroundWorker();
            Worker.RunWorkerCompleted += OnWorkerRunWorkerCompleted;
            Worker.DoWork += OnWorkerDoWork;
        }
    }
}
